/*================================================================
*   Copyright (C) 2022 Sangfor Ltd. All rights reserved.
*   
*   文件名称：oj_compile.hpp
*   创 建 者：wudu
*   创建日期：2022年04月25日
*   描    述：
*
================================================================*/
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>

#include <string>
#include <atomic>

#include <jsoncpp/json/json.h>
#include "oj_tools.hpp"
using namespace std;

/*
 * 提供一个接口给http模块进行使用
 * */
class Compiler{
    public:
        /*
         * resp_json：
         *    code ： xxx
         *    stdin ： xxx
         * */
        static void compile_and_run(Json::Value& requ_json, Json::Value& resp_json){
            //1.检查代码存在不存在
            if(requ_json["code"].empty()){
                resp_json["errorno"] = -1;
                resp_json["reason"] = "code is empty";
                return;
            }
            //2.将代码保存到文件
            string fileheader = WriteCodeToFile(requ_json["code"].asString());
            if(fileheader.empty()){
                resp_json["errorno"] = -2;
                resp_json["reason"] = "Intenal Error";
                return;
            }
            
            //3.编译
            if(Compile(fileheader) == false){
                resp_json["errorno"] = -3;
                string reason;
                FileUtil::ReadFile(CompileErrFileName(fileheader), &reason);
                resp_json["reason"] = reason;
                return;
            }
            
            //4.运行
            if(Run(fileheader) == false){
                resp_json["errorno"] = -4;
                string reason;
                FileUtil::ReadFile(StderrFileName(fileheader), &reason);
                resp_json["reason"] = reason;
                return;
            } 
            
            //5.构造响应
            resp_json["errorno"] = 0;
            resp_json["reason"] = "compile and run ok";

            string stdout_info;
            FileUtil::ReadFile(StdoutFileName(fileheader), &stdout_info);
            resp_json["stdout"] = stdout_info;
            
            //6.删除临时文件
            DeleteTmpFile(fileheader);
        }
    private:
        static void DeleteTmpFile(const string& fileheader){
            unlink(SrcFileName(fileheader).c_str());
            unlink(ExeFileName(fileheader).c_str());
            unlink(CompileErrFileName(fileheader).c_str());
            unlink(StdoutFileName(fileheader).c_str());
            unlink(StderrFileName(fileheader).c_str());
        }

        static bool Run(const string& fileheader){
            pid_t ret = fork();
            if(ret < 0){
                perror("fork");
                return false;
            }else if(ret == 0){
                //child
                int stdout_fd = open(StdoutFileName(fileheader).c_str(), O_CREAT | O_WRONLY, 0666);
                if(stdout_fd < 0){
                    exit(1);
                }
                
                int stderr_fd = open(StderrFileName(fileheader).c_str(), O_CREAT | O_WRONLY, 0666);
                if(stderr_fd < 0){
                    exit(1);
                }


                dup2(stdout_fd, 1);
                dup2(stderr_fd, 2);

                execlp(ExeFileName(fileheader).c_str(), ExeFileName(fileheader).c_str(), NULL);
                close(stdout_fd);
                close(stderr_fd);
                exit(1);
            }else{
                //father
                int status;
                wait(&status);

                if(status & 0x7f){
                    printf("sub process exec failed, recv signal\n");
                    return false;
                }
            }
            return true;
        }


        static bool Compile(const string& fileheader){
            //创建子进程， 让子进程进行进程程序替换为g++， 进行编译
            /*
             * 1.创建子进程
             * 2.子进程进行程序替换， 同时让父进程进行进程等待
             * 3.子进程替换成为g++进行编译
             * */

            pid_t ret = fork();
            if(ret < 0){
                return false;
            }else if(ret == 0){
                //child
                int fd = open(CompileErrFileName(fileheader).c_str(), O_CREAT | O_WRONLY, 0666);
                /*
                 * 提前进行重定向， 将标准错误的内容， 重定向到文件当中
                 * 0: 标准输入
                 * 1：标准输出
                 * 2：标准错误
                 * */
                dup2(fd, 2);

                execlp("g++", "g++", SrcFileName(fileheader).c_str(), "-o", ExeFileName(fileheader).c_str(), "-D", "CompileOnline", NULL);
                close(fd);
                exit(1);
            }else{
                //father - 阻塞的等待子进程
                wait(NULL);
            }

            struct stat st;
            if(stat(SrcFileName(fileheader).c_str(), &st) < 0){
                return false;
            }
            return true;
        }

        static string StdoutFileName(const string& fileheader){
            return "./tmp_file/" + fileheader + ".stdout";
        }

        static string StderrFileName(const string& fileheader){
            return "./tmp_file/" + fileheader + ".stderr";
        }

        static string CompileErrFileName(const string& fileheader){
            return "./tmp_file/" + fileheader + ".compilerr";
        }
        
        static string ExeFileName(const string& fileheader){
            return "./tmp_file/" + fileheader + ".exe";
        }

        static string SrcFileName(const string& fileheader){
            return "./tmp_file/" + fileheader + ".cpp";
        }

        static string WriteCodeToFile(const string& code){
            //1.组织当前文件的名字（时间戳， 原子性的序号）
            //tmp_时间戳_原子性的序号.cpp
            static atomic_uint id(0);
            string fileheader = "tmp_" + to_string(TimeUtil::GetTimeStmap()) + "_" + to_string(id);
            id++;
           
            //2.将代码写到文件
            FileUtil::WriteFile(SrcFileName(fileheader), code);
            return fileheader;
        }
};

